#include "../core/device/device.h"
#include "draw.h"

namespace sleek
{
    namespace texture
    {
        void drawCorner(core::math::array<core::math::vector2di> *storage, core::math::vector2di pos, core::u32 radius, core::u32 start, core::u32 end)
        {
            core::f32 p = sqrt(radius*radius);
            if(end > start) for(register core::f32 i = start; i<=end; i += p) storage->push_back(core::math::vector2di(sin(i DegToRad)*radius+pos.width, cos(i DegToRad)*radius+pos.height));
            else if(end < start) for(register core::f32 i = start; i<=end; i -= p) storage->push_back(core::math::vector2di(sin(i DegToRad)*radius+pos.width, cos(i DegToRad)*radius+pos.height));
            else return;
        }

        draw::draw(core::device::Device* dev) : src(dev)
        {
            quad  = glGenLists(1);
            quadc = glGenLists(1);
            cubec = glGenLists(1);

            glNewList(quad , GL_COMPILE); glBegin(GL_QUADS); glQuad(); glEnd(); glEndList();
            glNewList(quadc, GL_COMPILE); glBegin(GL_QUADS); glQuadCenter(); glEnd(); glEndList();
            glNewList(cubec, GL_COMPILE); glBegin(GL_QUADS); glCubeCenter(); glEnd(); glEndList();
            setActiveMaterial(new mesh::material());
        }
        draw::~draw()
        {
            glDeleteLists(quad , 1);
            glDeleteLists(quadc, 1);
            glDeleteLists(cubec, 1);
        }
        void draw::beginTo2D()
        {
            glMatrixMode(GL_PROJECTION);
            glPushMatrix();
            glLoadIdentity();
            glOrtho(0, src->getInfo().size.width, src->getInfo().size.height, 0, -1.f, 1.f);
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            glLoadIdentity();
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_CULL_FACE);
        }
        void draw::endFrom2D()
        {
            glEnable(GL_DEPTH_TEST);
            glMatrixMode(GL_PROJECTION);
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
            glPopMatrix();
        }
        void draw::ObjectRenderBegin()
        {
            if(!src->ready()) return;
            glPushMatrix();
            if(mat) mat->begin();
        }
        void draw::ObjectRenderEnd()
        {
            if(!src->ready()) return;
            if(mat) mat->end();
            glPopMatrix();
        }
        void draw::drawPixel(core::math::vector2di pos, pixel clr)
        {
            if(!src->ready()) return;
            ObjectRenderBegin();
                beginTo2D();
                    glColor4ub(clr.red, clr.green, clr.blue, clr.alpha);
                    glBegin(GL_POINTS); glVertex2f(pos.width, pos.height); glEnd();
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawPixel(core::math::vector3df pos, pixel clr)
        {
            if(!src->ready()) return;
            ObjectRenderBegin();
                glColor4ub(clr.red, clr.green, clr.blue, clr.alpha);
                glBegin(GL_POINTS); glVertex3f(pos.x, pos.y, pos.z); glEnd();
            ObjectRenderEnd();
        }
        void draw::drawLine(core::math::vector2di a, core::math::vector2di b, pixel clr)
        {
            if(!src->ready() || a>b) return;
            ObjectRenderBegin();
                beginTo2D();
                    glBegin(GL_LINES);
                        glColor4ub(clr.red, clr.green, clr.blue, clr.alpha);
                        glVertex2f(a.width, a.height);
                        glVertex2f(b.width, b.height);
                    glEnd();
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawLine(core::math::vector3df a, core::math::vector3df b, pixel clr)
        {
            if(!src->ready() || a>b) return;
            ObjectRenderBegin();
                glBegin(GL_LINES);
                    glColor4ub(clr.red, clr.green, clr.blue, clr.alpha);
                    glVertex3f(a.x, a.y, a.z);
                    glVertex3f(b.x, b.y, b.z);
                glEnd();
            ObjectRenderEnd();
        }
        void draw::drawArc(core::math::vector2di pos, core::u32 radius, core::u32 start, core::u32 end, pixel clr)
        {
            if(!src->ready()) return;
            core::f32 p = sqrt(radius*radius);
            core::math::vector2di vdata[] = { core::math::vector2di(sin(start DegToRad)*radius+pos.width, cos(start DegToRad)*radius+pos.height), pos };
            for(core::f32 i = start+p; i<end+p; i += p)
            {
                vdata[1] = core::math::vector2di(sin(i DegToRad)*radius+pos.width, cos(i DegToRad)*radius+pos.height);
                drawLine(vdata[0], vdata[1], clr);
                vdata[0] = vdata[1];
            }
        }
        void draw::drawCube(core::math::aabbox2di c, core::math::vector3df rot, pixel clr)
        {
            if(!src->ready()) return;
            core::math::vector2di pos = c.center();

            ObjectRenderBegin();
                beginTo2D();
                    glTranslatef(pos.width,pos.height,0);
                    glRotatef(rot.x,rot.y,rot.z);
                    glScalef(c.getSize().width, c.getSize().height,1);
                    glBegin(mat->getMode()); glColor4ub(clr.red, clr.green, clr.blue, clr.alpha); glQuadCenter(); glEnd();
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawCube(core::math::aabbox3df c, core::math::vector3df rot, pixel clr)
        {
            if(!src->ready()) return;
            core::math::vector3df pos = c.center();

            ObjectRenderBegin();
                glTranslatef(pos.x,pos.y,pos.z);
                glRotatef(rot.x,rot.y,rot.z);
                glScalef(c.getSize().x, c.getSize().y, c.getSize().z);
                glBegin(GL_QUADS); glColor4ub(clr.red, clr.green, clr.blue, clr.alpha); glCubeCenter(); glEnd();
            ObjectRenderEnd();
        }
        void draw::drawCube(core::math::vector2di a, core::math::vector2di b, core::math::vector3df rot, pixel clr)
        {
            drawCube(core::math::aabbox2di(a,b), rot, clr);
        }
        void draw::drawCube(core::math::vector3df a, core::math::vector3df b, core::math::vector3df rot, pixel clr)
        {
            drawCube(core::math::aabbox3df(a,b), rot, clr);
        }
        void draw::drawCircle(core::math::vector2di pos, core::u32 radius, pixel clr)
        {
            if(!src->ready()) return;
            core::f32 p = sqrt(radius*radius);
            core::math::vector2di start = core::math::vector2di(sin(0 DegToRad)*radius, cos(0 DegToRad)*radius);
            for(register core::f32 i = 0; i<360; i += 12)
            {
                core::math::vector2di end = core::math::vector2di(sin(i DegToRad)*radius, cos(i DegToRad)*radius);
                drawLine(start+pos, end+pos, clr);
                start = end;
            }
        }
        void draw::drawRoundedCube(core::math::aabbox2di a, core::u32 r, pixel c)
        {
            drawRoundedCube(a.getUpperLeft(), a.getLowerRight(), r, c);
        }
        void draw::drawRoundedCube(core::math::vector2di a, core::math::vector2di b, core::u32 r, pixel c)
        {
            if(!src->ready()) return;
            if(a > b){ core::math::vector2di ab = a; a = b; b = ab; }
            if(b-a <= core::math::vector2di(r*2)) drawCircle(a+(r/2), r/2, c);
            else
            {
                core::math::array<core::math::vector2di> vertices;
                    drawCorner(&vertices, core::math::vector2di(b.width-r, b.height-r), r,   0,  90); // bottom right
                    drawCorner(&vertices, core::math::vector2di(b.width-r, a.height+r), r,  90, 180); // top right
                    drawCorner(&vertices, core::math::vector2di(a.width+r, a.height+r), r, 180, 270); // top left
                    drawCorner(&vertices, core::math::vector2di(a.width+r, b.height-r), r, 270, 360); // bottom left
                drawPolygonList(vertices.pointer(), vertices.size(), core::math::vector2di(0,0), core::math::vector3df(0,0,0), c);
                vertices.clear();
            }
        }
        void draw::drawPolygon(core::math::vector2di a, core::math::vector2di b, core::math::vector2di c, pixel clr)
        {
            drawLine(a, b, clr);
            drawLine(b, c, clr);
            drawLine(c, a, clr);
        }
        void draw::drawPolygon(core::math::vector3df a, core::math::vector3df b, core::math::vector3df c, pixel clr)
        {
            drawLine(a, b, clr);
            drawLine(b, c, clr);
            drawLine(c, a, clr);
        }
        void draw::drawPolygonList(core::math::vector2di *a, core::u32 n, core::math::vector2di pos, core::math::vector3df rot, pixel clr)
        {
            if(!src->ready()) return;
            ObjectRenderBegin();
                beginTo2D();
                    glTranslatef(pos.width,pos.height,0);
                    glRotatef(rot.x,rot.y,rot.z);
                        glBegin(mat->getMode());
                            glColor4ub(clr.red, clr.green, clr.blue, clr.alpha);
                            for(core::u32 i = 0; i< n; ++i) glVertex2f(a[i].width,a[i].height);
                            glColor4ub(255,255,255,255);
                        glEnd();
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawPolygonList(core::math::vector3df *a, core::u32 n, core::math::vector3df pos, core::math::vector3df rot, pixel clr)
        {
            if(!src->ready()) return;
            ObjectRenderBegin();
                glTranslatef(pos.x,pos.y,pos.z);
                glRotatef(rot.x,rot.y,rot.z);
                glBegin(mat->getMode());
                    glColor4ub(clr.red, clr.green, clr.blue, clr.alpha);
                    for(core::u32 i = 0; i< n; ++i)
                        glVertex3f(a[i].x,a[i].y, a[i].z);
                    glColor4ub(255,255,255,255);
                glEnd();
            ObjectRenderEnd();
        }
        void draw::drawFBO(fbo *tex)
        {
            if(!src->ready() || !tex) return;
            ObjectRenderBegin();
                beginTo2D();
                    glEnable(GL_TEXTURE_2D);
                    tex->bind();
                    glColor4ub(255,255,255,255);
                    glTranslatef(0,0,0);
                    glScalef(tex->getSize().width,tex->getSize().height,1);
                    glCallList(quad);
                    glDisable(GL_TEXTURE_2D);
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawTexture(texture *tex, core::math::vector2di pos,  core::math::vector3df rot, core::math::vector2df scale)
        {
            if(!src->ready() || !tex) return;
            if(!glIsTexture(tex->glTexture())) tex->updateGl();
            ObjectRenderBegin();
                beginTo2D();
                    glEnable(GL_TEXTURE_2D);
                    glBindTexture(GL_TEXTURE_2D, tex->glTexture());
                    glTranslatef(pos.width,pos.height+tex->getOriginalSize().height,0);
                    glRotatef(180+rot.x,rot.y,rot.z);
                    glScalef(tex->getOriginalSize().width*scale.width,
                             tex->getOriginalSize().height*scale.height,1);
                    glCallList(quad);
                    glDisable(GL_TEXTURE_2D);
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawTexture(texture *tex, core::math::vector3df pos, core::math::vector3df rot, core::math::vector2df scale)
        {
            if(!src->ready() || !tex) return;
            if(!glIsTexture(tex->glTexture())) tex->updateGl();
            ObjectRenderBegin();
                glEnable(GL_TEXTURE_2D);
                glEnable(GL_DEPTH_TEST);
                glBindTexture(GL_TEXTURE_2D, tex->glTexture());
                glTranslatef(pos.x,pos.y,pos.z);
                glRotatef(rot.x,rot.y,rot.z);
                glScalef(tex->getOriginalSize().width*scale.width,
                         tex->getOriginalSize().height*scale.height,1);
                glCallList(quadc);
                glDisable(GL_TEXTURE_2D);
            ObjectRenderEnd();
        }
        void draw::drawTextureCenter(texture *tex, core::math::vector2di pos, core::math::vector3df rot, core::math::vector2df scale)
        {
            if(!src->ready() || !tex) return;
            if(!glIsTexture(tex->glTexture())) tex->updateGl();
            ObjectRenderBegin();
                beginTo2D();
                    glEnable(GL_TEXTURE_2D);
                    glColor4ub(255,255,255,255);
                    glBindTexture(GL_TEXTURE_2D, tex->glTexture());
                    glTranslatef(pos.width,pos.height,0);
                    glRotatef(180+rot.x,rot.y,rot.z);
                    glScalef(tex->getOriginalSize().width*scale.width,
                             tex->getOriginalSize().height*scale.height,1);
                    glCallList(quadc);
                    glDisable(GL_TEXTURE_2D);
                endFrom2D();
            ObjectRenderEnd();
        }
        void draw::drawTextureCenter(texture *tex, core::math::vector3df pos, core::math::vector3df rot, core::math::vector2df scale)
        {
            if(!src->ready() || !tex) return;
            if(!glIsTexture(tex->glTexture())) tex->updateGl();
            ObjectRenderBegin();
                glEnable(GL_DEPTH_TEST);
                glEnable(GL_TEXTURE_2D);
                glColor4ub(255,255,255,255);
                glBindTexture(GL_TEXTURE_2D, tex->glTexture());
                glTranslatef(pos.x,pos.y,pos.z);
                glRotatef(rot.x,rot.y,rot.z);
                glScalef(tex->getOriginalSize().width*scale.width,
                         tex->getOriginalSize().height*scale.height,1);
                glCallList(quadc);
                glDisable(GL_TEXTURE_2D);
            ObjectRenderEnd();
        }
        void draw::drawMesh(mesh::MeshBuffer *m, core::math::vector3df pos, core::math::vector3df rot)
        {
            if(!src->ready() || !m || !m->vertices.size()) return;
            ObjectRenderBegin();
                glColor4ub(255,255,255,255);
                glTranslatef(pos.x,pos.y,pos.z);
                glRotatef(rot.x,rot.y,rot.z);
                glScalef(mat->getScale().x,mat->getScale().y,mat->getScale().z);
                if(m->isActivedVBO())
                {
                    glEnableClientState(GL_NORMAL_ARRAY);
                    glEnableClientState(GL_VERTEX_ARRAY);
                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                    glEnableClientState(GL_COLOR_ARRAY);
                    glEnableClientState(GL_INDEX_ARRAY);

                    m->bindVBO(mesh::MeshBuffer::NORMAL_BUFFER);
                    glNormalPointer(GL_FLOAT, 0, 0);

                    m->bindVBO(mesh::MeshBuffer::VERTEX_BUFFER);
                    glVertexPointer(3, GL_FLOAT, 0, 0);

                    for(register char t = 0; t<GL_MAX_TEXTURE; ++t)
                    {
//                        glActiveTextureARB(GL_TEXTURE0_ARB+t);
                        m->bindVBO(mesh::MeshBuffer::TEXTURE_COORD_BUFFER);
                        glTexCoordPointer(2, GL_FLOAT, 0, 0);
                    }

                    m->bindVBO(mesh::MeshBuffer::COLOR_BUFFER);
                    glColorPointer(4, GL_UNSIGNED_BYTE, 0, 0);

                    m->bindVBO(mesh::MeshBuffer::INDEX_BUFFER);
                    glIndexPointer(GL_UNSIGNED_BYTE, 0, 0);

                    glDrawArrays(mat->getMode(), 0, m->indices.size()*3);
//                    glDrawElements(mat->getMode(), m->indices.size()*3, GL_UNSIGNED_INT, 0);

                    glDisableClientState(GL_COLOR_ARRAY);
                    glDisableClientState(GL_INDEX_ARRAY);
                    glDisableClientState(GL_NORMAL_ARRAY);
                    glDisableClientState(GL_VERTEX_ARRAY);
                    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                }
                else
                {
                    glBegin(mat->getMode());
                    for(core::u32 i = 0; i<m->indices.size(); ++i)
                    {
                        for(core::u32 v = 0; v<3; ++v)
                        {
                            register int c = m->indices[i].vertex[v];
                            if(c < m->vertices.size())
                            {
                                glColor4ub(m->vertices[c].Color.red, m->vertices[c].Color.green, m->vertices[c].Color.blue, m->vertices[c].Color.alpha);
                                glNormal3f(m->vertices[c].Normal.x,m->vertices[c].Normal.y, m->vertices[c].Normal.z);
//                              glTexCoord2f(m->vertices[c].Coord.width,m->vertices[c].Coord.height);
                                for(register char t = 0; t<GL_MAX_TEXTURE; ++t)
                                    glMultiTexCoord2fARB(GL_TEXTURE0_ARB+t, m->vertices[c].Coord.width,m->vertices[c].Coord.height);
                                glVertex3f(m->vertices[c].Pos.x,m->vertices[c].Pos.y, m->vertices[c].Pos.z);
                            }
                        }
                    }
                    glEnd();
                }
            ObjectRenderEnd();
        }
        void draw::setActiveMaterial(mesh::material *i)
        {
            mat = i;
        }
        mesh::material* draw::getActiveMaterial()
        {
            return mat;
        }
    }
}
